SurfaceFlinger中VSYNC信号的控制同步
SurfaceFlinger(简称SF)的绘制合成过程是在VSYNC信号(即垂直同步信号)的控制下同步进行的,所以VSYNC信号可以说是SF的指挥官,它的协调同步控制对于界面绘制效率至关重要。本篇将介绍VYSNC信号在SF服务中是如何发挥这个指挥官的角色。
SurfaceFlinger中的VSYNC
SufaceFlinger的初始化是在init方法中进行的,这个方法中关于VYSNC信号有两个DispSyncSource,分别为App绘制延时源和SF合成延时源,这两个信号源基于同一个VSYNC信号模型mPrimaryDispSync,它是一个DispSync对象,DispSync是对硬件Hwc垂直信号的同步模型,那么为什么在有硬件VSYNC信号的情况下还需要一个这样的同步模型呢?实际上,这个是Android系统的一种优化策略,因为在VYSNC信号到来后,App绘制和SF合成过程如果此时同时进行,可能会竞争CPU,从而会影响绘制效率,为了避免竞争引入了VYSNC同步模型DispSync,该模型会根据需要打开硬件的VYSNC信号进行采样,然后同步VSYNC信号模型,从而为上层的绘制延时源和合成延时源提供VYSNC信号,基于该同步模型,绘制延时源和合成延时源可以分别在此基础上添加一个相位偏移量(vsyncPhaseOffsetNs和sfVsyncPhaseOffsetNs),以此错开绘制和合成在VYSNC信号到来后的执行。
1 | void SurfaceFlinger::init() { |
硬件VSYNC信号的产生
在介绍VSYNC信号如何来协调绘制和合成过程前,我们先看硬件VSYNC信号是如何产生并传递给同步模型DispSync。首先硬件的垂直信号是通过显示设备产生的,它通过HAL层的HWComposer模块将硬件垂直信号发送给SF。
1 | //frameworks/native/services/surfaceflinger/DisplayHardware/HWComposer.cpp |
HWComposer对象负责SF的硬件合成,理所当然VSYNC信号也应该由其提供,在其构造方法中,会加载hwc设备模块,并将VSYNC信号的回调hook_vsync注册到hwc设备中这样硬件产生的VSYNC信号就可以回调给HWCoposer对象的hook_vsync。需要注意的是HWComposer并不一定就是底层存在的硬件设备,它也可以代表一个虚拟设备,这样VSYNC信号就是通过一个VSyncThread线程模拟硬件产生的。
1 | //通知HWComposer垂直事件到达 |
在hook_vsync方法中进一步调用HWComposer的vsync方法通知VSYNC信号事件,在vsync方法中,最终是通过EventHandler的onVsyncReceived方法通知给SF的,这个EventHandler是在构造HWComposer时由SF提供的,实际上SF本身就是继承自HWComposer::EventHandler,而HWComposer::EventHandler的定义如下:
1 | class HWComposer |
所以VSYNC信号是通过vsync方法的mEventHandler回调onVsyncReceived通知给SF,我们看看SF是如何实现的onVsyncReceived方法
1 | void SurfaceFlinger::onVSyncReceived(int type, nsecs_t timestamp) { |
到这一步,我们看到硬件产生的同步信号最终是交给同步模型DispSync的addResyncSample方法,根据该方法的返回值,可以根据需要控制硬件是否继续发送垂直信号,可见,硬件的垂直信号并不是持续产生的,而是同步模型在需要的时候才打开的,而什么时候需要,是由addResyncSample计算得到的。
这里我们顺便看看硬件的垂直信号是如何打开和关闭的。
1 | //开启硬件Vsync |
硬件垂直信号的开关是通过mEventControlThread的setVysncEnable方法控制的,在SF的init方法中会创建这个mEventControlThread,它是一个EventControlThread,也是一个线程。
1 | void EventControlThread::setVsyncEnabled(bool enabled) { |
在threadLoop中,它一开始默认的就通过SF的eventControl将硬件的VSYNC信号关闭,然后进入到while循环中阻塞,当通过setVsyncEnabled设置了mVsyncEnabled并唤醒线程后,根据设置的状态,通过SF的eventControl将通知hwc开关VSYNC信号。当然,在SF初始化完成后会打开硬件的VSYNC信号。这个流程我简单的介绍下。
SurfaceFlinger::initializeDisplays
SurfaceFlinger::onInitializeDisplays
SurfaceFlinger::onScreenAcquired
SurfaceFlinger::resyncToHardwareVsync
1 | void SurfaceFlinger::resyncToHardwareVsync(bool makeAvailable) { |
SF在init中会对显示设备进行一次初始化,这个初始化的过程最终会通过resyncToHardwareVsync根据显示设备设置同步模型的刷新频率同时也会打开硬件的VSYNC信号,以此为VSYNC同步模型做好同步的准备。
DispSync同步模型
硬件VSYNC是如何产生并交给同步模型的过程我们已经清楚了,接下来,我们看看同步模型是如何处理硬件的同步信号并为上层的监听者提供VSYNC信号的。
1 | //Vsync的同步模型对象 默认会启动一个线程进行信号同步 |
在DisySync构造方法中会默认的开启一个同步线程DispSyncThread,这个线程负责进行VYSNC信号的同步同时会将垂直同步信号发送给感兴趣的监听者(比如我们在SF的init方法中创建的两个延时源DispSyncSource)
1 | class DispSyncThread: public Thread { |
DispSyncThread的逻辑很简单,它通过一个while循环,来不断的通过computeNextEventTimeLocked计算下一次VSYNC信号的时间,然后通过gatherCallbackInvocationsLocked收集要通知的监听者,最后通过fireCallbackInvocations来通知他们VSYNC信号的到达事件。这里最关键的是VYSNC信号的计算过程,同步模型是如何按照监听者的延时要求提供VSYNC信号的,它是如何保证VSYNC信号的精度和有序性的?以及模型出现误差后是如何做出调整?要回答这些问题,需要对同步模型的整个机制进行完整和全面的了解。这里为了保证文章篇幅,我们不会对同步模型做详细的描述,这里只简单的描述下即可。
前面我们知道SF在接收到硬件的VSYNC信号后通过addResyncSample方法来将信号发送给同步模型的进行样本统计。下面我们看看其实现,
1 | bool DispSync::addResyncSample(nsecs_t timestamp) { |
在该方法中,同步模型会收集硬件的Vsync信号的时间戳信息到mResyncSamples中,最多保存MAX_RESYNC_SAMPLES(定义为32)个硬件的VSYNC信号的时间信息,其中mNumResyncSamples和mFirstResyncSample构成了一个大小为32的VSYNC信号窗口,这个窗口最多可以包含32份硬件的VSYNC信号的时间戳信息,其中mFirstResyncSample是窗口的第一个VSYNC信号样本,而mNumResyncSamples表示已经有多少个信号样本。有了这些样本,就可以基于此来更新我们的同步模型的来使其和硬件的VSYNC信号同步。这个是通过updateModelLocked方法来完成的。
1 | void DispSync::updateModelLocked() { |
updateModelLocked方法根据统计的样本来更新同步模型,只有当样本数大于等于MIN_RESYNC_SAMPLES_FOR_UPDATE(定义为3)时才进行模型的更新,当样本数大于等于3时先通过样本计算mPerid,这个值时计算方式是统计所有相邻样本的时间间隔总和到durationSum中,然后除以样本数减1就是样本的频率mPeriod。接下来计算模型的偏移,因为现在 mPeriod 算出来的是平均值,所以并不是真的硬件vsync时间间隔就是 mPeriod, 存在着误差,即有些样本信号的时间间隔大于平均值,而有些样本时间间隔小于平均值,而这些与mPriod的差值就是偏移,下面就是要算出这些平均的偏移值,计算偏移值后会将偏移值mPhase和时间间隔mPeriod更新到模型中。
1 | void updateModel(nsecs_t period, nsecs_t phase) { |
随后在同步模型线程中computeNextEventTimeLocked基于统计样本计算的mPeriod和mPhase计算下一次的VSYNC信号,接下来我们看看computeNextEventTimeLocked是如何实现的。
1 | //得到最接近的下次VSYNC信号的时间 |
computeNextEventTimeLocked针对所有的监听者计算下一次的VSYNC信号的发生时间,并将最接近当前时间的一次作为结果返回,而每个监听者的下一次VSYNC信号的发生时间可能是不同,因为他们可能设置了不同的偏移,因此针对每个监听者计算下一次VSYNC信号的发生时间是通过computeListenerNextEventTimeLocked完成的。
同步模型并不一定完全准确,每次计算可能都会有误差的出现,当出现误差后,则需要更新误差值,根据误差值来判断是否需要开启硬件VSYNC重新添加样本到同步模型中进行计算。这个过程是在SF的postCompostion中进行的。
1 | void SurfaceFlinger::postComposition() |
在postComposition中先拿到当前设备的Fence,然后通过addPresentFence计算同步模型的误差值,根据误差值来决定是否需要启用硬件VSYNC。
通知监听者
1 | //收集回调,根据回调的延时偏量计算是否要触发回调 |
计算完下次要触发VSYNC信号的时间后,可能需要等待一段时间,因为当前时间还未到达最近的触发事件,当到达触发的时间后,同步模型线程会通过gatherCallbackInvocationsLocked收集需要进行通知的监听者,如果监听者的下次VSYNC信号发生时间已经小于本次VSYNC信号的触发时间,则说明监听者需要进行通知了,将其添加到集合中等待回调。
1 | //回调对同步模型的VSync信号感兴趣的监听者 |
最后通过监听者的添加的回调通知其VSYNC信号已经到达。
绘图延时源和合成延时源(DispSyncSource)
在SF的init方法中我们知道SF创建了两个延时源对象DispSyncSource,他们基于同步模型来处理VSYNC信号,这两个不同的延时源通过两个不同的EventThread来管理,他们分别为mEventThread和mSFEventThread,这里的EventThread是为了延时源方便管理VSYNC信号,比如对于绘图延时源,它的监听者就有大名鼎鼎的Choreographer,上层App的绘制过程正是在Choreographer的协调下同步进行的,而Choreographer正是注册到绘图延时源的EventThread中以此来监听VSYNC信号,而SF是注册到合成延时源的EventThread中。
下面我们看看由同步模型传递给延时源的VSYNC信号是如何使用传递给需要的监听者的。
1 | class DispSyncSource : public VSyncSource, private DispSync::Callback {//VsyncSource定义在EventThread.h |
延时源通过同步模型DispSync来构造,同步模型通过接口onDispSyncEvent上报给延时源VSYNC信号,并通过延时源设置的回调callback的onVSyncEvent方法将VSYNC信号的到达事件发送给设置者(事实上就是EventThread)。同时,延时源可以通过setVSyncEnabled方法来控制是否监听来自于同步模型的VSYNC信号。
EventThread
EventThread顾明思议,它实际上也是一个Thread,它创建的时候就会启动该线程.我们看看它的线程回调
1 | //frameworks/native/services/surfaceflinger/EventThread.cpp |
线程回调方法首先通过waitForEvent等待VSYNC信号的通知,同时获取到需要通知的Connection,这里的Connection就是延时源VSYNC信号的监听者,随后通过Connection的postEvent方法将事件发送给监听者,所以EventThread最核心的内容应该是在waitForEvent中进行处理的。
1 | //当接收到vsync信号时接收到 或者至少有一个连接对VSYNC信号感兴趣此方法返回给调用者 |
垂直事件到达后会将其保存在参数event中,如果timestamp是0,表示没有VSYNC信号到达。mDisplayEventConnections中保存了已经注册的监听者。如果此时connection的count大于等于0,则表示有监听者对VSYNC信号感兴趣,同时置waitForVSync为true,同时将该监听者添加到signalConnections集合中。这里connection的count值的含义如下:
- count >= 1 : continuous event. count is the vsync rate 如果在大于等于1,表示会持续接收vsync event
- count == 0 : one-shot event that has not fired 表示只接收一次
- count ==-1 : one-shot event that fired this round / disabled 等于-1,表示不能再接收vsync事件了
当满足timestamp && !waitForVSync时表示VSYNC信号已经到达,但是此时没有感兴趣的监听者,所以此时不需要再接收VSYNC信号了,通过disableVSyncLocked移除对同步模型的VSYNC信号的监听。
当!timestamp && waitForVSync满足时则说明有感兴趣的监听者,但VSYNC信号还未达到,这时候需要使用enableVSyncLocked将延时源添加到同步模型的监听者集合中。
如果!timestamp && !eventPending则表示没有VSYNC信号到达,且没有其他等待的事件,但此时如果waitForVSync为true,则要等待VSYNC信号,但此时不会无限等待下去,而是有一个超时时间。
当VSYNC信号到达后,通过EventThread设置的callback来接收来自同步模型的VSYNC信号,这个回调就是EventThread的onVSyncEvent方法
1 | void EventThread::onVSyncEvent(nsecs_t timestamp) { |
在onVSyncEvent方法中,会解除在waitForEvent中等待VSYNC信号的阻塞状态。从而退出while循环,通知相应的监听者VSYNC信号的到达。
总结
至此,我们就将VSYNC信号在SF中的传递和控制过程介绍完了,我们简单总结下整个传递过程:
- 硬件或者由软件模拟触发VSYNC信号,通知给SF
- SF接收到硬件的VSYNC信号后将其添加到同步模型DispSync的样本数组中进行统计和计算模型的偏移和周期
- 同步模型根据计算的偏移和周期计算下次VSYNC信号发生时间,并通知监听者VSYNC信号到达的事件
- 同步模型的VSYNC信号传递给延时源,延时源通过EventThread来管理VSYNC信号的收发
参考
Android 5.1 SurfaceFlinger VSYNC详解
https://blog.csdn.net/newchenxf/article/details/49131167
DispSync
https://echuang54.blogspot.com/2015/01/dispsync.html
Android中的GraphicBuffer同步机制-Fence
https://blog.csdn.net/jinzhuojun/article/details/39698317